import socket
import os
import json
import struct
import logging
class SimpleFileServer:
    """一个简单的文件传输服务器"""
    def __init__(self, host = '', port = 8080, shared_dir = 'D://MyCode//python_project//Python-Network-Programming//Best Practice//文件传输//面向对象版//server//share'):
        """
        初始化文件服务器
        
        :param host: 服务器绑定地址，默认为所有接口
        :param port: 服务器监听端口
        :param shared_dir: 共享文件目录路径
        """

        self.host = host
        self.port = port
        self.shared_dir = shared_dir
        self.server_socket = None
        self.logger = self.setup_logger()

        #创建文件共享目录
        self.create_directory(shared_dir)

    def start(self):
        """启动文件服务器"""
        try:
            # 创建服务器套接字
            self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            #设置地址重用选项
            #socket.setsockopt(level, option, value)
            # level：指定选项所在的协议层。
            # option：要设置的具体选项。
            # value：要为选项设置的值。
            self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            #绑定地址和端口
            self.server_socket.bind((self.host, self.port))
            #开始监听
            self.server_socket.listen(1) #一次只监听一个端口
            self.logger.info(f'服务器启动，监听端口{self.port}')
            #主循环
            while True:
                self.logger.info('等待客户但连接...')
                #连接客户端
                client_socket, client_addr = self.server_socket.accept()
                self.logger.info(f'客户端已连接：{client_addr}')
                #处理客户端请求
                self.handle_client(client_socket)
                #关闭客户端连接
                client_socket.close()
                self.logger.info(f'客户端连接已关闭：{client_addr}')
        except KeyboardInterrupt:
            self.logger.info('服务器关闭中...')
        except Exception as e:
            self.logger.info(f'服务器错误：{str(e)}')
        finally:
            if self.server_socket:
                self.server_socket.close()
            self.logger.info('服务器已关闭')

    def handle_client(self, client_socket):
        """处理客户但请求"""
        try:
            while True:
                command_data = client_socket.recv(1024)
                if not command_data:
                    self.logger.info('客户端已断开连接')
                    break
                
                #解码命令
                try:
                    command = command_data.decode('utf-8').strip()
                    self.logger.info(f'收到命令：{command}')
                except UnicodeDecodeError:
                    self.send_error(client_socket, '命令编码错误')
                    continue
                #处理命令
                self.process_command(client_socket, command)
        except Exception as e:
            self.logger.error(f'处理客户端时出错：{str(e)}')
    
    def process_command(self,client_socket, command):
        """处理客户但命令"""
        """
        split()用法：根据默认空白符(空格、换行、制表符等)，会忽略多个
        连续的空白符，返回分割后的字符串列表
        如：'get file.text' list = ['get', 'file.text']
        """
        parts = command.split()
        if not parts:
            self.send_error(client_socket, '空命令')
            return
        #获取请求命令和文件名
        command_type = parts[0].lower()
        filename = parts[1] if len(parts) > 1 else ''

        if command_type == 'get':
            self.handle_get_command(client_socket, filename)
        elif command_type == 'list':
            self.handle_list_command(client_socket)
        else:
            self.send_error(client_socket, f'未知命令{command_type}')

    def handle_get_command(self, client_socket, filename):
        """处理获取文件命令"""
        if not filename:
            self.send_error(client_socket, '缺少文件名')
            return
        #构建完整路径名
        # 安全拼接路径：将共享目录与客户端请求的文件名组合成完整路径
        # 例如：self.shared_dir = "D:/server/share", filename = "data.txt"
        # 结果 -> "D:/server/share/data.txt"
        file_path = os.path.join(self.shared_dir, filename)
        #检查文件是否存在
        if not os.path.exists(file_path):
            self.send_error(client_socket, f"文件不存在：{filename}")
            return
        if not os.path.isfile(file_path):
            self.send_error(client_socket, f'不是文件：{filename}')
            return
        #发送文件
        self.send_file(client_socket, file_path)

    def send_file(self, client_socket, file_path):
        """发送文件给客户端"""
        try:
            #获取文件信息
            #从完整路径中提取纯文件名（去除目录结构）
            # 示例输入："D:/server/share/sub/data.txt" → 输出："data.txt"
            filename = os.path.basename(file_path)
            # 检查文件是否存在
            # 验证文件真实存在的关键检查
            # 示例：file_path = "D:/server/share/data.txt"
            # 当文件不存在时 → 提前终止发送流程
            if not os.path.exists(file_path):
                self.send_error(client_socket, f"文件不存在：{filename}")
                return
            #获取文件大小的核心方法
            # 示例：file_path = "D:/server/share/data.txt" (文件大小 1024 字节)
            # 结果 -> 1024
            file_size = os.path.getsize(file_path)
        
            #创建文件头信息
            header = {
                'type' : 'file',
                'filename' : filename,
                'file_size' : file_size
            }

            #发送文件头
            self.send_json(client_socket, header)

            #读取并发送文件内容
            sent_bytes = 0

            with open(file_path, mode='rb') as file:
                while sent_bytes < file_size:
                    #每次读取1024字节
                    chunk = file.read(1024)
                    client_socket.send(chunk)
                    sent_bytes += len(chunk)
            self.logger.info(f'文件发送完成：{filename} ({file_size} 字节)')
        except Exception as e:
            self.send_error(client_socket, f'发送文件失败：{str(e)}')
    def send_json(self, client_socket, data):
        """发生json数据给客户端"""
        try:
            #将数据转换为json字符串
            json_data = json.dumps(data)
            #将json字符串转换为字节
            json_bytes = json_data.encode('utf-8')
            #发送数据长度（4字节整数）
            #网络协议封包的核心操作
            # 示例：json_bytes长度是 58 字节 → 打包为 4 字节大端无符号整数
            # 结果：b'\x00\x00\x00:'
            lenth = struct.pack('>I', len(json_bytes))
            client_socket.send(lenth)
            #发送json数据
            client_socket.send(json_bytes)
        except Exception as e:
            self.logger.error(f'发送JSON数据失败：{str(e)}')
    def setup_logger(self):
        """设置简单的日志记录器"""
        #logging.getLogger(name):获取创建一个指定名称的日志器实例
        #作用：日志器作为日志系统的入口，不同名称的日志器可以独立配置，FileServer,表示该日志器属于文件服务器模块
        logger = logging.getLogger('FileServer')
        #logger.setLevel(level):设置日志器最低日志级别
        #日志级别从低到高：DEBUG < INFO < WARING <ERROR < CRITICAL
        """
        DEBUG    → 调试信息 (数值=10)   # 最详细，用于开发调试
        INFO     → 信息性消息 (数值=20)  # 常规操作记录，如服务启动
        WARNING  → 警告信息 (数值=30)    # 潜在问题，但程序仍运行
        ERROR    → 错误信息 (数值=40)    # 严重问题，功能失效
        CRITICAL → 致命错误 (数值=50)    # 程序无法继续运行
        """
        logger.setLevel(logging.INFO)

        #创建控制台处理器
        #logging.StreamHandler():创建一个输出到控制台(流)的处理器
        #处理器(handler):决定日志的输出目的地(如控制台、文件、网络等)
        console_handler = logging.StreamHandler()
        #处理器也可以单独设置日志级别，用于过滤该处理器需要处理的日志，用法与日志器相同
        console_handler.setLevel(logging.INFO)
        #设置日志格式
        """
        logging.Formatter:定义日志输出格式，格式字符串的占位符含义：
        %(asctime)s：日志记录的时间（默认格式为 YYYY-MM-DD HH:MM:SS,mmm）。
        %(name)s：日志器的名称（这里是 'FileServer'）。
        %(levelname)s：日志级别（如 INFO、ERROR）。
        %(message)s：日志的具体内容（调用 logger.info("消息") 时传入的内容）。
        示例输出：2023-10-01 12:34:56,789 - FileServer - INFO - 共享目录已创建：D://share。
        """
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s -%(message)s')
        #handler.setFormatter(formatter):将定义好的格式应用到处理器，是该处理器输出的格式按指定格式显示
        console_handler.setFormatter(formatter)
        #避免重复添加处理器
        if not logger.handlers:
            #logger.addHandler(handler):将处理器添加到日志器中，日志器会将日志传递给所以已添加的处理器进行输出
            #一个日志器可以添加多个处理器
            logger.addHandler(console_handler)

        return logger

    def create_directory(self, path):
        """创建目录(如果不存在)"""
        try:
            #os.makedirs：python内置的创建目录的函数
            #path:要创建的目录路径(可以是多级目录，如a/b/c)
            #exist_ok = True:如果目录已存在，不会抛出异常(默认False时，目录存在会报错)
            #作用：递归创建目录(即如果路径中的父目录不存在，会一并创建)，且避免因目录已存在而报错
            os.makedirs(path, exist_ok=True)
            self.logger.info(f"共享目录已创建：{path}")
            """
            Exception：异常类型
            Exception 是 Python 中所有内置非系统退出类异常的基类（父类）。
            在 except Exception 中，它用于捕获所有继承自 Exception 的异常（几乎涵盖了程序运行中可能出现的所有常规错误，如文件不存在、权限不足、网络错误等）。
            举例：如果代码中出现 FileNotFoundError（文件未找到）、PermissionError（权限错误）等，都会被 Exception 捕获，因为这些具体异常都是 Exception 的子类。
            2. as e：异常实例
            as e 是将捕获到的具体异常对象赋值给变量 e。
            这个变量 e 包含了异常的详细信息，例如：
            异常的类型（可以通过 type(e) 查看）。
            异常的描述信息（可以通过 str(e) 或直接打印 e 获取，如错误原因）。
            举例：当创建目录失败时（如权限不足），e 会是一个 PermissionError 实例，str(e) 会返回类似 [Errno 13] Permission denied: 'xxx' 的具体错误信息，便于日志记录或调试。
            """
        except Exception as e:
            self.logger.error(f"创建目录失败：{str(e)}")
            #raise：将捕获的异常重新抛出，让调用者（如初始化服务器时）感知到错误，避免程序在目录创建失败的情况下继续运行（可能导致后续文件操作出错）
            raise
    def send_error(self, client_socket, message):
        """发送错误消息给客户端"""
        error_data = {
            'type' : 'error',
            'message' : message
        }
        self.send_json(client_socket, error_data)
        self.logger.error(f'发送错误消息：{message}')
#主程序入口的标准写法，当且仅当直接运行本文件时执行的代码，确保不会作为模块导入其他文件时不会被意外运行
if __name__ == "__main__":
    # 创建并启动服务器
    server = SimpleFileServer(
        shared_dir="D://MyCode//python_project//Python-Network-Programming//Best Practice//文件传输//面向对象版//server//share",  # 服务器文件存储目录
        port=8080
    )
    server.start()